/*
 * Copyright 2006-2007 S W McIlroy
 * Licensed  under the  Apache License,  Version 2.0  (the "License");
 * you may not use  this file  except in  compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed  under the  License is distributed on an "AS IS" BASIS,
 * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
 * implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.mockfu;

import static org.mockfu.Mocks.*;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Vector;
import java.util.regex.Pattern;

import org.testng.annotations.Test;

public class MocksTest
{

    @Test
    public void constraintTree()
    {
        class Integers
        {
            public void method( int a, int b, int c, int d )
            {
            }
        }

        MockControl<Integers> integers = MockControl.of( Integers.class );

        integers.expectsOnce().method( 1, not( leq( 6 ) ), not( and( lt( 2 ), geq( -10 ) ) ),
                                       or( 3, 4 ) );
        assertCall( integers,
                    "Integers.method(1, not(leq(6)), not(and(lt(2), geq(-10))), or(3, 4))" );

        integers.clear();
        integers.expectsOnce().method( or( not( 1 ), not( or( 2 ) ), 3 ), lt( 0 ), eq( -83209775 ),
                                       and( eq( 7 ), not( 8 ) ) );
        assertCall( integers,
                    "Integers.method(or(not(1), not(or(2)), 3), lt(0), -83209775, and(7, not(8)))" );

        integers.clear();
        integers.expectsOnce().method( and( 0, not( not( 1 ) ) ), any( int.class ), or( or( -1 ) ),
                                       not( any( int.class ) ) );
        assertCall( integers, "Integers.method(and(0, not(not(1))), any(), or(or(-1)), not(any()))" );
    }

    @Test
    public void anyMatcher()
    {
        assertThat( 0, any( int.class ) );
        assertThat( Long.MIN_VALUE, any( long.class ) );
        assertThat( "", any( String.class ) );
        assertThat( -83209775, any( int.class ) );
        assertThat( null, any( Integer.class ) );
        assertThat( 0, or( null, any( int.class ) ) );
    }

    @Test
    public void leqMatcher()
    {
        assertThat( -2, leq( 5 ) );
        assertThat( 5.0, leq( 5.0 ) );
        assertFails( "Integer less than or equal to 5", 1010, leq( 5 ) );
        assertFails( "Double less than or equal to 5.0", 5.01, leq( 5.0 ) );
    }

    @Test
    public void ltMatcher()
    {
        assertThat( Integer.MIN_VALUE, lt( -12 ) );
        assertThat( -13, lt( -12 ) );
        assertFails( "Integer less than -12", -12, lt( -12 ) );
        assertFails( "Short less than -12", (short) 0, lt( (short) -12 ) );
    }

    @Test
    public void geqMatcher()
    {
        assertThat( -1, geq( -1 ) );
        assertThat( 100, geq( -1 ) );
        assertFails( "Long greater than or equal to -100000000001", -100000000002L,
                     geq( -100000000001L ) );
    }

    @Test
    public void gtMatcher()
    {
        assertThat( Integer.MAX_VALUE, gt( 5 ) );
        assertThat( 5.0, gt( 4.9 ) );
        assertFails( "Float greater than 5.0", 5.0f, gt( 5.0f ) );
        assertFails( "Byte greater than 5", (byte) -10, gt( (byte) 5 ) );
    }

    @Test
    public void notMatcher()
    {
        assertThat( 0, not( 1 ) );
        assertThat( -1, not( 1 ) );
        assertThat( 1.0000000000001, not( 1.0 ) );
        assertFails( "Integer not equal to 1", 1, not( 1 ) );
    }

    @Test
    public void andMatcher()
    {
        assertThat( -10, and( lt( 10 ), geq( -10 ), not( 1 ) ) );
        assertThat( 0, and( lt( 10 ), geq( -10 ), not( 1 ) ) );
        assertThat( -1, and( lt( 10 ), geq( -10 ), not( 1 ) ) );
        assertFails( "Integer less than 10 and greater than or equal to -10 but not equal to 1",
                     -100, and( lt( 10 ), geq( -10 ), not( 1 ) ) );
        assertFails( "Integer less than 10 and greater than or equal to -10 but not equal to 1",
                     100, and( lt( 10 ), geq( -10 ), not( 1 ) ) );
        assertFails( "Integer less than 10 and greater than or equal to -10 but not equal to 1", 1,
                     and( lt( 10 ), geq( -10 ), not( 1 ) ) );
    }

    @Test
    public void orMatcher()
    {
        assertThat( 2, or( 2, 3, gt( 5 ) ) );
        assertThat( 3, or( 2, 3, gt( 5 ) ) );
        assertThat( 100, or( 2, 3, gt( 5 ) ) );

        assertFails( "Integer equal to 2 or 3 or greater than 5", 1, or( 2, 3, gt( 5 ) ) );
        assertFails( "Integer equal to 2 or 3 or greater than 5", 4, or( 2, 3, gt( 5 ) ) );
        assertFails( "Integer equal to 2 or 3 or greater than 5", 5, or( 2, 3, gt( 5 ) ) );
        assertFails( "Integer equal to 2 or 3 or greater than 5", 5, or( 2, 3, gt( 5 ) ) );
    }

    @Test
    public void nulls()
    {
        assertThat( null, (Object) null );
        assertThat( null, is( null ) );
        assertThat( "", (String) not( null ) );
        assertThat( null, or( is( null ), new Object() ) );
        assertThat( null, or( is( null ), eq( null ) ) );
        assertThat( "", or( eq( "" ), (String) not( null ) ) );

        assertFails( "String equal to \"[not null]\"", null, "not null" );
        assertFails( "String null", "", null );
        assertFails( "Object not null", null, not( null ) );
    }

    @Test( expectedExceptions = IllegalStateException.class )
    public void ambiguousNull()
    {
        try
        {
            assertThat( null, or( is( null ), null ) );
        }
        catch( IllegalStateException ex )
        {
            assertThat( ex.getMessage(), matches( ".*clarify using eq\\(null\\).*" ) );
            throw ex;
        }
    }

    @Test
    public void isAMatcher()
    {
        assertThat( (Object) "", isA( CharSequence.class ) );
        assertThat( "", isA( String.class ) );
        assertThat( new Object(), not( isA( StringBuilder.class ) ) );
        assertThat( null, or( isA( StringBuilder.class ), eq( null ) ) );
        assertThat( "!", and( isA( String.class ), not( "" ) ) );

        assertFails( "Object of class java.lang.StringBuilder", null, isA( StringBuilder.class ) );
        assertFails( "String of class java.lang.String but not equal to \"\"", "",
                     and( isA( String.class ), not( "" ) ) );

        MockControl<Vector<CharSequence>> vector = new MockControl<Vector<CharSequence>>() {};
        vector.allows().add( isA( String.class ) );
        vector.mock().add( "" );
    }

    @Test
    public void isMatcher()
    {
        Object obj = new Object();
        assertThat( obj, is( obj ) );
        assertThat( new String(), not( is( "" ) ) );

        String ref = new String();
        String identity = Integer.toHexString( System.identityHashCode( ref ) );
        assertFails( "String \"\" (@" + identity + ")", new String(), is( ref ) );
        String ref2 = new String();
        String identity2 = Integer.toHexString( System.identityHashCode( ref2 ) );
        assertFails( "String \"\" (@" + identity + ") but not \"\" (@" + identity2 + ")",
                     new String(), and( is( ref ), not( is( ref2 ) ) ) );
    }

    @Test
    public void booleans()
    {
        MockControl<Map<Boolean, Boolean>> map = new MockControl<Map<Boolean, Boolean>>() {};
        map.expectsOnce().put( eq( false ), not( true ) );

        map.mock().put( false, false );
        map.verify();

        assertThat( false, false );
        assertThat( false, not( true ) );
        assertThat( true, true );
        assertFails( "Boolean equal to false", true, false );
    }

    @Test( expectedExceptions = IllegalStateException.class )
    public void ambiguousBoolean()
    {
        MockControl<Map<Boolean, Boolean>> booleans = new MockControl<Map<Boolean, Boolean>>() {};
        booleans.expectsOnce().put( is( false ), false );
        booleans.verify();
    }

    @Test
    public void arrays()
    {
        assertThat( new String[] { "netstat ".trim(), "-tln ".trim() }, new String[] { "netstat",
                "-tln" } );
        assertThat( new String[] { "netstat", "-tln" }, new String[] { "netstat", "-tln" } );
        assertFails( "String[] of [\"nets[d]tat\", \"-tln\"]", new String[] { "netstat", "-tln" },
                     new String[] { "netsdtat", "-tln" } );
        assertThat( new String[] { "netstat", "-xyz" }, new String[] { "netstat",
                any( String.class ) } );
        assertFails( "String[] of [\"netstat\", not equal to \"-tln\"]", new String[] { "netstat",
                "-tln" }, new String[] { "netstat", not( "-tln" ) } );

        String[] array = new String[1];
        assertThat( array, or( is( array ), any( String[].class ) ) );
    }

    public interface VarargsTest
    {
        public void test( String arg, String... args );
    }

    @Test
    public void varargs()
    {
        MockControl<VarargsTest> varargs = MockControl.of( VarargsTest.class );
        varargs.expectsOnce().test( (String) not( null ), any( String.class ), "foo",
                                    matches( "..." ) );
        assertCall( varargs, "VarargsTest.test(not(null), {any(), \"foo\", matches(/.../)})" );
    }

    @Test
    public void containsMatcher()
    {
        assertThat( Arrays.asList( 0, 9, 3, -18, 5 ), contains( 5, 9, -18 ) );
        assertThat( Arrays.asList( 0, 3, -18, 9, 5 ), contains( 5, 9, or( -18, 2 ) ) );
        assertThat( Arrays.asList( "there", "hey" ), contains( "hey", "hey" ) );
        assertFails( "Collection containing {5, 9, -18 or 2}", Arrays.asList( 0, 3, -18 ),
                     contains( 5, 9, or( -18, 2 ) ) );
        assertFails( "Collection not containing \"hello\"", Arrays.asList( "hello" ),
                     not( contains( "hello" ) ) );

        MockControl<HashSet<String>> hashSet = new MockControl<HashSet<String>>() {};
        hashSet.allows().addAll( contains( "hello", not( "goodbye" ) ) );
        hashSet.mock().addAll( Arrays.asList( "hello", "sayanora" ) );
    }

    @Test
    public void equalsSetMatcher()
    {
        assertThat( Arrays.asList( "a", "movie", "got", "me", "a" ), equalsSet( "got", "me", "a",
                                                                                "movie" ) );
        assertThat( Arrays.asList( "a", "movie", "got", "me", "a" ), equalsSet( "got", "me", "a",
                                                                                "movie", "movie" ) );
        assertThat( Arrays.asList( 1, 2, 2903 ), equalsSet( 2, 2903, 1, 1 ) );
        assertFails( "Collection set equal to {2, 2903, 1, 0}", Arrays.asList( 1, 2, 2903 ),
                     equalsSet( 2, 2903, 1, 0 ) );
    }

    @Test
    public void matchesMatcher()
    {
        assertThat( "ZiGA", matches( "(?i).*zig.*" ) );
        assertFails( "String matching /.*zig\\b.*/", "ZiGA", matches( ".*zig\\b.*" ) );
    }

    @Test( expectedExceptions = IllegalStateException.class )
    public void matchesNested()
    {
        assertThat( "", matches( not( "." ) ) );
    }

    @Test
    public void strings()
    {
        assertFails( "String equal to \"p[umpkin] pie\"", "peach pie", "pumpkin pie" );
        assertFails( "String equal to \"[pumpkin] pie\"", "apple pie", "pumpkin pie" );
        assertFails( "String equal to \"pu[mpkin pie]\"", "punch", "pumpkin pie" );
        assertFails( "String equal to \"pumpkin[s] pie\"", "pumpkin pie", "pumpkins pie" );
        assertFails( "String equal to \"[pumpkin pie]\"", "ice cream", "pumpkin pie" );
        assertFails( "String equal to \"pumpkin p[]ie\"", "pumpkin pumpkin pie", "pumpkin pie" );
        assertFails( "String equal to \"pumpkin []pie\"", "pumpkin seed pie", "pumpkin pie" );
        assertFails( "String equal to \"pum[pkin] pie\"", "pum pie", "pumpkin pie" );
        assertFails( "String equal to \"[pumpkin pie]\"", "", "pumpkin pie" );
        assertFails( "String equal to \"[]pie\"", "cherry pie", "pie" );
        assertFails( "String equal to \"p[umpkin p]ie\"", "pie", "pumpkin pie" );
        assertFails( "String equal to \"[pumpkin pie]\"", null, "pumpkin pie" );
        assertFails( "String equal to \"[]\"", null, "" );
        assertFails( "String equal to \"peach[ pie]\" or \"p[umpkin]s\"", "peaches",
                     or( "peach pie", "pumpkins" ) );
        assertFails( "String not equal to \"pumpkin pie\"", "pumpkin pie", not( "pumpkin pie" ) );
        assertFails( "String not equal to \"\"", "", not( "" ) );
    }

    @Test( expectedExceptions = IllegalArgumentException.class )
    public void misplacedConstraint()
    {
        MockControl<Object> object = MockControl.of( Object.class );

        Object other = new Object();
        object.expectsOnce().equals( eq( is( other ) ) );
    }


    private void assertFails( String text, Object actual, Object constraint )
    {
        try
        {
            assertThat( actual, constraint );
        }
        catch( AssertionError ex )
        {
            Pattern p = Pattern.compile( ".*Expected: (.*)\n.*", Pattern.DOTALL );
            java.util.regex.Matcher m = p.matcher( ex.getMessage() );
            if( m.matches() )
            {
                assertThat( m.group( 1 ), text );
                return;
            }
            throw new AssertionError( "Assertion message didn't match expected pattern: "
                    + ex.getMessage() );
        }
        throw new AssertionError( "Expected assertion to fail" );
    }

    private void assertCall( MockControl<?> control, String expectedCall )
    {
        try
        {
            control.verify();
        }
        catch( ExpectationFailure ex )
        {
            Pattern p = Pattern.compile( "Expected (.*) exactly once.*" );
            java.util.regex.Matcher m = p.matcher( ex.getMessage() );
            if( m.matches() )
            {
                assertThat( m.group( 1 ), expectedCall );
                return;
            }
        }
        throw new AssertionError();
    }
}
